#include <buola/gui.h>

#include <buola/image/cfont.h>
#include <buola/gui/cicon.h>
#include <buola/gui/cconfig.h>
#include <buola/image/ctextlayout.h>
#include <buola/image/cimage.h>
#include <buola/image/cgc.h>
#include <buola/utility/cregion.h>

#include <buola/widgets/clistbox.h>
#include <buola/widgets/cscrollbar.h>
#include <buola/widgets/ceditbox.h>

#include "clblistrenderer.h"

namespace buola { namespace gui {

class CLBListRendererData
{
public:
    //preshape
    double mHeight;             //minimum height for the item
    CSize_d mIconSize;            

    //shape
    CPoint_d mIconPos;            //position inside the whole item
    CPoint_d mTextPos;            //inside the item
    img::CTextLayout mTextLayout;

    //pos
    CPoint_d mPos;                //absolute position of the item
};

CLBListRenderer::CLBListRenderer(CListBox *pListBox)
            :CLBRenderer(pListBox)
{
    mFont=mListBox->GetStyle(CWidget::STYLE_SMALLFONT)?img::CFont::GetStock(L"small"):
                                            img::CFont::GetStock(L"control");

    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        InitItem(mListBox->GetItems()[i]);
    }

    CalcAll();
}

CLBListRenderer::~CLBListRenderer()
{
    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        DeinitItem(mListBox->GetItems()[i]);
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
//////////////////////// Event handling ///////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///////////////////// Internal functions //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CLBListRenderer::CalcAll()
{
    if(mListBox->GetStyle()&CListBox::STYLE_NOICON)
        mIconSize={0,0};
    else
        mIconSize=CSize_d(mListBox->GetIconSize());
    
    mTextEnd=mListBox->Size().x;
    mTotalEnd=mTextEnd;
    
    mMinTextHeight=mFont->GetHeight();
    PreshapeAll();
    ShapeAll();
    PosAll();
}

void CLBListRenderer::InitItem(CListBox::SItem *pI)
{
    CLBListRendererData *lD=new CLBListRendererData;
    
    pI->mRendererData=lD;
}

void CLBListRenderer::DeinitItem(CListBox::SItem *pI)
{
    delete (CLBListRendererData*)pI->mRendererData;
    pI->mRendererData=nullptr;
}

void CLBListRenderer::PreshapeItem(CListBox::SItem *pI)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;
    
    if(pI->mImage)
    {
        lD->mIconSize=pI->mImage.Size();
    }
    else
    {
        lD->mIconSize=mIconSize;
    }

    lD->mHeight=max(lD->mIconSize.y,mMinTextHeight)+4;

    if(lD->mIconSize.x>mMaxIconWidth) 
    {   
        mMaxIconWidth=lD->mIconSize.x;
    }
}

void CLBListRenderer::ShapeItem(CListBox::SItem *pI)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;

//Shape icon

//Pos icon
    lD->mIconPos.x=2;
    lD->mIconPos.y=lD->mHeight/2-lD->mIconSize.y/2;
            
//Shape & pos text
    lD->mTextPos.x=mMaxIconWidth?(mMaxIconWidth+8.0):5.0;
    lD->mTextLayout.SetDefaultFont(mFont);
    lD->mTextLayout.SetText(pI->mText);
    lD->mTextLayout.SetWidth(mTextEnd-lD->mTextPos.x);
    lD->mTextLayout.SetHeightLines(1);
    lD->mTextPos.y=lD->mHeight/2-lD->mTextLayout.GetSize().y/2;
}

void CLBListRenderer::DrawItem(img::CGC *pGC,CListBox::SItem *pI)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;
    
    CPoint_d lPos=lD->mPos-mListBox->GetScrollPos();
    CPoint_d lTextPos=lPos+lD->mTextPos;

    if(pI->mSelected)
    {
        pGC->SetSource(gConfig.mColors.Get(L"selection_back"));
        pGC->Path().Rect(lPos,CSize_d(mTotalEnd,lD->mHeight));
        pGC->FillPath();
    }
    
    if(pI->mImage)
    {
        pGC->DrawImage(lPos+lD->mIconPos,pI->mImage);
    }
    else if(pI->mIcon)
    {
        pI->mIcon->Draw(pGC,lPos+lD->mIconPos,mListBox->GetIconSize(),
                        pI->mSelected?CIcon::SELECTED:CIcon::NORMAL);
    }
    
    pGC->SetSource(gConfig.mColors.Get(pI->mSelected?L"selection_text":L"edit_text"));
    
    lD->mTextLayout.Draw(lTextPos,pGC);
}

void CLBListRenderer::RedrawItem(CListBox::SItem *pI,bool pRedraw)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;
    
    CRect_d lRect(lD->mPos-mListBox->GetScrollPos(),
                CSize_d(mTotalEnd,lD->mHeight));

    img::CGC *lGC=mListBox->StartPainting(CRegion(lRect),pRedraw);
    lGC->SetFont(mFont);
    DrawItem(lGC,pI);

    if(mListBox->mSelecting&&mListBox->mLastDragPoint!=mListBox->mDragPoint)
    {
        CRegion lInt(lRect);
        lInt&=CRect_d(mListBox->mLastDragPoint,mListBox->mDragPoint);
        
        lGC->SetSource(img::CColor(0x7f7f6028));
        ///\todo lGC->FillRegion(lInt);
    }

    mListBox->EndPainting(lGC,CRegion(lRect));
}

int CLBListRenderer::HitTestItem(CListBox::SItem *pI,const CPoint_d &pP)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;
    
    CPoint_d lP=pP-lD->mPos+mListBox->GetScrollPos();
    
    if(lP.x<0||lP.y<0||lP.x>=mTotalEnd||lP.y>=lD->mHeight)
        return LBHT_NONE;
    
    if(CRect_d(lD->mIconPos,lD->mIconSize).Contains(lP))
        return LBHT_ICON|LBHT_SELECT|LBHT_REGION;
    
    if(CRect_d(lD->mTextPos,lD->mTextLayout.GetSize()).Contains(lP))
        return LBHT_TEXT|LBHT_SELECT|LBHT_REGION;
    
    return LBHT_REGION|LBHT_SELECT;
}

int CLBListRenderer::HitTestItem(CListBox::SItem *pI,const CRect_d &pR)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;
    
    CRect_d lR=pR-lD->mPos+mListBox->GetScrollPos();
    
    if(lR.r<0||lR.b<0||lR.l>=mTotalEnd||lR.t>=lD->mHeight)
        return LBHT_NONE;
    
    if(lR.l<0&&lR.t<0&&lR.r>=mTotalEnd&&lR.b>=lD->mHeight)
        return LBHT_TOTAL;
    
    if(lR.r>=lD->mIconPos.x&&lR.b>=lD->mIconPos.y&&
            lR.l<lD->mIconPos.x+lD->mIconSize.x&&
            lR.t<lD->mIconPos.y+lD->mIconSize.y)
        return LBHT_PARTIAL;
    
    return LBHT_NONE;    
}

void CLBListRenderer::PreshapeAll()
{
    mMaxIconWidth=0;
    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        PreshapeItem(mListBox->GetItems()[i]);
    }
}

void CLBListRenderer::ShapeAll()
{
    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        ShapeItem(mListBox->GetItems()[i]);
    }
}

void CLBListRenderer::PosAll(int /*pFrom*/)
{
    CSize_d lSize=mListBox->Size();

    double lY=0;

    for(int i=0;i<mListBox->GetItemCount();i++)
    {
        CListBox::SItem *lI=mListBox->GetItems()[i];
        CLBListRendererData *lD=(CLBListRendererData*)lI->mRendererData;

        lD->mPos.x=0;
        lD->mPos.y=lY;
        lY+=lD->mHeight;
    }

    mTotalHeight=lY;

    int lScrollRange=(int)(lY-lSize.y);

    if(lScrollRange<=0)
    {
        mListBox->GetVScroll()->SetRange(0,0);
        mListBox->GetVScroll()->Hide();
    }
    else
    {
        mListBox->GetVScroll()->SetRange(0,lScrollRange);
        mListBox->GetVScroll()->SetStep(
                    (int)max(mIconSize.y,mMinTextHeight),(int)lSize.y);
        mListBox->GetVScroll()->Show();
    }

    mListBox->GetHScroll()->SetRange(0,0);
    mListBox->GetHScroll()->Hide();
}

void CLBListRenderer::DrawAll(img::CGC *pGC)
{
    for(int i=0;i<mListBox->GetItemCount();i++)
    {
        CListBox::SItem *lI=mListBox->GetItems()[i];
        CLBListRendererData *lD=(CLBListRendererData*)lI->mRendererData;

        if(lD->mPos.y-mListBox->GetScrollPos().y>mListBox->Size().y+1||
           lD->mPos.y-mListBox->GetScrollPos().y+lD->mHeight<0) continue;
        
        DrawItem(pGC,lI);
    }
}

void CLBListRenderer::DrawAll(img::CGC *pGC,const CRect_d &pRect)
{
    for(int i=0;i<mListBox->GetItemCount();i++)
    {
        CListBox::SItem *lI=mListBox->GetItems()[i];
        CLBListRendererData *lD=(CLBListRendererData*)lI->mRendererData;

        if(lD->mPos.y-mListBox->GetScrollPos().y>pRect.b+1||
           lD->mPos.y-mListBox->GetScrollPos().y+lD->mHeight<pRect.t) continue;
        
        DrawItem(pGC,lI);
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CLBListRenderer::OnSize(const CGeometryEvent &pE)
{
    CalcAll();
}

void CLBListRenderer::OnExpose(const CExposeEvent &pE)
{
    img::CGC *lGC=pE.mGC;
    
    lGC->SetSource(gConfig.mColors.Get(L"edit_text"));
    lGC->SetFont(mFont);
    DrawAll(lGC,pE.mRect);
}    

void CLBListRenderer::OnBigChange()
{
    CalcAll();
    mListBox->Refresh();
}

void CLBListRenderer::OnItemSelected(CListBox::SItem *pItem)
{
    RedrawItem(pItem,true);
}

void CLBListRenderer::OnItemAdded(CListBox::SItem *pItem)
{
    InitItem(pItem);
    PreshapeItem(pItem);
    ShapeItem(pItem);

    if(mListBox->GetAutoUpdate())
    {
        PosAll();
        RedrawItem(pItem,false);
    }
}

void CLBListRenderer::OnItemChanged(CListBox::SItem *pItem)
{
    ShapeItem(pItem);
    RedrawItem(pItem,true);
}

void CLBListRenderer::OnItemRemoved(CListBox::SItem *pI)
{
    DeinitItem(pI);

    if(mListBox->GetAutoUpdate())
    {
        PosAll();
        mListBox->Refresh();
    }
}

void CLBListRenderer::OnXScrollChanged(int /*pValue*/)
{
}

PEditBox CLBListRenderer::Edit(CListBox::SItem *pI)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;

    PEditBox lEditBox=new CEditBox;
//  lEditBox->Create(mListBox,lD->mPos+lD->mTextPos-mListBox->GetScrollPos()-
//      CPoint_d(3,3),CSize_d(mTextEnd-lD->mPos.x,lD->mTextLayout.GetSize().y+5),pI->mText);
    lEditBox->MoveResize(lD->mPos,CSize_d(mListBox->Size().x,lD->mHeight));
    lEditBox->SetCaption(pI->mText);
    lEditBox->SetStyleBit(CWidget::STYLE_NOBORDER);
    lEditBox->SelectAll();
    mListBox->Place(lEditBox);
    lEditBox->SetFocus();

    return lEditBox;
}

CPoint_d CLBListRenderer::GetIconPos(CListBox::SItem *pI)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;

    return lD->mPos+lD->mIconPos-mListBox->GetScrollPos();
}

CRect_d CLBListRenderer::GetItemRect(CListBox::SItem *pI)
{
    CLBListRendererData *lD=(CLBListRendererData*)pI->mRendererData;

    return CRect_d(lD->mIconPos-mListBox->GetScrollPos(),lD->mIconSize);
}

/*namespace gui*/ } /*namespace buola*/ }
